﻿using System;
using System.IO;
using Gmantis.Zip.ZLib;

namespace Gmantis.Zip
{
    public class ZStreamReader : Stream
    {
        // Fields
        private Stream _baseStream;
        private byte[] _buf;
        private long _length;
        private bool _noMoreInput;
        private bool _ownsBase;
        private byte[] _rb;
        private long _start;
        private bool _stored;
        private byte[] _xb;
        private int _xlen;
        private int _xpos;
        private ZStream _z;
        private const int BUFFERSIZE = 0x8000;

        // Methods
        internal ZStreamReader()
        {
            this._rb = new byte[1];
            this._xb = new byte[0x8000];
        }

        public ZStreamReader(Stream baseStream)
        {
            this._rb = new byte[1];
            this._xb = new byte[0x8000];
            this.Init(baseStream, true, false);
        }

        public ZStreamReader(Stream baseStream, bool zip)
        {
            this._rb = new byte[1];
            this._xb = new byte[0x8000];
            bool header = !zip;
            bool flag2 = zip;
            this.Init(baseStream, header, flag2);
        }

        public ZStreamReader(Stream baseStream, bool header, bool crc32)
        {
            this._rb = new byte[1];
            this._xb = new byte[0x8000];
            this.Init(baseStream, header, crc32);
        }

        public ZStreamReader(Stream baseStream, bool zip, int sizeCompressed)
        {
            this._rb = new byte[1];
            this._xb = new byte[0x8000];
            bool header = !zip;
            bool flag2 = zip;
            this.Init(baseStream, header, flag2);
            this._length = sizeCompressed;
        }

        public ZStreamReader(Stream baseStream, bool zip, int sizeCompressed, int method)
            : this(baseStream, zip, (long)sizeCompressed, method)
        {
        }

        public ZStreamReader(Stream baseStream, bool zip, long sizeCompressed, int method)
        {
            this._rb = new byte[1];
            this._xb = new byte[0x8000];
            bool header = !zip;
            bool flag2 = zip;
            this.Init(baseStream, header, flag2);
            this._length = sizeCompressed;
            this._stored = method == 0;
            if ((method != 0) && (method != 8))
            {
                throw new ZStreamException(StringTables.GetString("Compression method not supported by C1Zip."));
            }
        }

        public override void Close()
        {
            if (this._ownsBase)
            {
                this._baseStream.Close();
            }
        }

        public override void Flush()
        {
            this._baseStream.Flush();
        }

        internal void Init(Stream baseStream, bool header, bool crc32)
        {
            if ((baseStream == null) || !baseStream.CanRead)
            {
                throw new ArgumentException(StringTables.GetString("ZStreamReader needs a readable stream."));
            }
            this._noMoreInput = false;
            this._buf = new byte[0x8000];
            this._baseStream = baseStream;
            this._start = 0L;
            this._length = -1L;
            this._stored = false;
            try
            {
                this._start = baseStream.Position;
            }
            catch
            {
            }
            this._z = new ZStream(crc32);
            this._z.next_in_index = 0;
            this._z.avail_in = 0;
            this._z.next_in = this._buf;
            if (this._z.inflateInit(header ? 15 : -15) != 0)
            {
                throw new ZStreamException(StringTables.GetString("Failed to initialize compressed stream."));
            }
        }

        private int read(byte[] b, int offset, int len)
        {
            int num = 0;
            while (num < len)
            {
                for (int i = this._xpos; (i < this._xlen) && (num < len); i++)
                {
                    b[offset++] = this._xb[this._xpos++];
                    num++;
                }
                if (num < len)
                {
                    this._xpos = 0;
                    this._xlen = this.readInternal(this._xb, 0, this._xb.Length);
                    if (this._xlen == 0)
                    {
                        return num;
                    }
                }
            }
            return num;
        }

        public override int Read(byte[] buf, int offset, int count)
        {
            return this.read(buf, offset, count);
        }

        public override int ReadByte()
        {
            if (this.read(this._rb, 0, 1) != 0)
            {
                return this._rb[0];
            }
            return -1;
        }

        private int readInternal(byte[] b, int off, int len)
        {
            int num5;
            if (len == 0)
            {
                return 0;
            }
            this._z.next_out = b;
            this._z.next_out_index = off;
            this._z.avail_out = len;
        Label_0029:
            if ((this._z.avail_in == 0) && !this._noMoreInput)
            {
                int length = this._buf.Length;
                if (this._length >= 0L)
                {
                    length = (int)Math.Min((long)length, this._length - this.Position);
                }
                if (length > 0)
                {
                    this._z.next_in_index = 0;
                    this._z.avail_in = this._baseStream.Read(this._buf, 0, length);
                    this._noMoreInput = this._z.avail_in < length;
                }
                else
                {
                    this._noMoreInput = true;
                }
            }
            if (!this._stored)
            {
                int num2 = this._z.inflate(0);
                switch (num2)
                {
                    case -5:
                        if (this._noMoreInput)
                        {
                            num2 = 0;
                        }
                        break;

                    case 1:
                        num2 = 0;
                        this._noMoreInput = true;
                        break;

                    case 0:
                        goto Label_01BE;
                }
                throw new ZStreamException(StringTables.GetString("Error inflating: ") + this._z.msg);
            }
            int num3 = Math.Min(this._z.avail_in, this._z.avail_out);
            for (int i = 0; i < num3; i++)
            {
                this._z.next_out[this._z.next_out_index + i] = this._z.next_in[this._z.next_in_index + i];
            }
            this._z.next_out_index += num3;
            this._z.next_in_index += num3;
            this._z.avail_in -= num3;
            this._z.avail_out -= num3;
        Label_01BE:
            num5 = len - this._z.avail_out;
            if ((num5 <= 0) && !this._noMoreInput)
            {
                goto Label_0029;
            }
            return num5;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException(StringTables.GetString("Seek not supported."));
        }

        public override void SetLength(long value)
        {
            this._length = value;
        }

        public override void Write(byte[] buf, int offset, int count)
        {
            throw new NotSupportedException(StringTables.GetString("Write not supported."));
        }

        // Properties
        public Stream BaseStream
        {
            get
            {
                return this._baseStream;
            }
        }

        public override bool CanRead
        {
            get
            {
                return true;
            }
        }

        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return false;
            }
        }

        public override long Length
        {
            get
            {
                if (this._length >= 0L)
                {
                    return this._length;
                }
                return 0L;
            }
        }

        public bool OwnsBaseStream
        {
            get
            {
                return this._ownsBase;
            }
            set
            {
                this._ownsBase = value;
            }
        }

        public override long Position
        {
            get
            {
                return (this._baseStream.Position - this._start);
            }
            set
            {
                throw new NotSupportedException(StringTables.GetString("Seek not supported."));
            }
        }

        public int SizeCompressed
        {
            get
            {
                if (this._z.total_in > 0x7fffffffL)
                {
                    throw new ZipFileException(StringTables.GetString("Size is too large to be represented as an Int32, use SizeCompressedLong instead."));
                }
                return (int)this._z.total_in;
            }
        }

        public long SizeCompressedLong
        {
            get
            {
                return this._z.total_in;
            }
        }

        public int SizeUncompressed
        {
            get
            {
                if (this._z.total_out > 0x7fffffffL)
                {
                    throw new ZipFileException(StringTables.GetString("Size is too large to be represented as an Int32, use SizeUncompressedLong instead."));
                }
                return (int)this._z.total_out;
            }
        }

        public long SizeUncompressedLong
        {
            get
            {
                return this._z.total_out;
            }
        }

        public ZStream ZStream
        {
            get
            {
                return this._z;
            }
        }
    }
}
